package algo_lignes_sites.data.path;

import algo_lignes_sites.data.Data_ls;
import algo_lignes_sites.data.junction.A_Junction;
import algo_lignes_sites.data.junction.JunctionDER;
import algo_lignes_sites.data.junction.JunctionREF;
import algo_lignes_sites.data.segment.A_AlgolineSegment;
import algo_lignes_sites.params.objects.contraintesgeom.ContrainteGeomLine;
import commons.data.Model.TypeField;
import commons.data.*;
import commons.drawing.I_Drawable;
import commons.ihm.ParametresAffichageCarto;
import commons.ihm.ParametresAffichageCarto.ModeAffichage;
import commons.ihm.ParametresAffichageCarto.TypeAffichage;
import commons.logs.Logger;
import commons.params_appli.ParamsAppli;
import commons.spatialindex.I_SpatialIndexable;
import commons.spatialindex.simplegrid.SimpleGridSPI;
import commons.utils.Utils;
import commons.utils.Utils.Side;
import commons.utils.UtilsCalcul;
import commons.utils.UtilsGeom;
import commons.utils.primitive_geom.Point_d;
import commons.utils.primitive_geom.Rect_d;
import commons.utils.primitive_geom.Segment_d;
import commons.utils.primitive_geom.Vector_d;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.regex.Pattern;

public abstract class A_Path implements I_SpatialIndexable, I_Drawable, I_Searchable, Comparable<A_Path> {

	//**************************************************************************
	//**** DONNEES
	//**************************************************************************
	public abstract ArrayList<A_Junction> getVJunction();
	public abstract ArrayList<A_AlgolineSegment> getVSegment();

	// ATTRIBUTS (fusion)
	protected Attributes attributes;
	public Attributes getAttributes() {return attributes;}
	public Model getModel() {return attributes.getModel();}

	// ZONE
	// Les objets font toujours partie de la zone principale par défaut
	// ils peuvent éventuellement faire partie d'1 ou plusieurs sous-zone
	protected Zones zones;
	public Zones getZones() {return zones;}


	// ECHELLES DE GENERALISATION
	// Contient l'échelle maximum à laquelle doit être "grossi" le path durant le traitement
	protected EchelleGeneralization echelleGeneralization;
	public void setEchelleGeneralization(EchelleGeneralization eg) {this.echelleGeneralization = eg;}
	public EchelleGeneralization getEchelleGeneralization() {return echelleGeneralization;}

	// CONTRAINTES GEOMETRIQUES
	protected ContrainteGeomLine contrainteGeom;
	public void setContrainteGeom(ContrainteGeomLine cgl) {this.contrainteGeom = cgl;}
	public ContrainteGeomLine getContrainteGeom() {return contrainteGeom;}
	public boolean hasDissymetrie() {return contrainteGeom.hasDissymetrie();}

	// Pour la phase d'optimisation des Dissymetrie
	public boolean bOrientationSetted;
	public int nbTotal_barbule;
	public int nbIntersect_barbuleDroite;
	public int nbIntersect_barbuleGauche;



	// REPRESENTATION
	protected Color color;
	public Color getColor() {return color;}
	public void setColor(Color color) {this.color=color;}

	static protected String champLibelleLine;
	static protected String champTriLine;
	static protected TypeField typeChampTriLine; // va servir pour faciliter le tri
	static protected boolean bBiggestOnTop;
	public static void setChampLibelleLine(String champLibelleLine) {A_Path.champLibelleLine=champLibelleLine;}
	// a n'appeler qu'une fois, et avant toute instantiation d'un A_Path -> car la valeur pour la comparaison est precalculée une seule fois dans le constructeur !
	public static void setChampTriLine(String champTriLine, TypeField typeChampTriLine) {A_Path.champTriLine=champTriLine; A_Path.typeChampTriLine = typeChampTriLine;}
	public static void setbBiggestOnTop(boolean bBiggestOnTop) {A_Path.bBiggestOnTop=bBiggestOnTop;}

	protected Comparable valueToCompare_forDisplay;

	// Traduction des contraintes geom (valeur sur papier) en contraintes en mètre
	// Cette traduction est réalisée par majContraintesSelonEchelle() à chaque changement d'échelle
	// -> epaisseur trait
	public float epaisseurTrait_reel_m;
	// -> espace minimum à respecter de part et d'autre du path
	public float espaceAutour_reel_m;
	// -> epaisseur de l'éventuelle dissymétrie
	public float epaisseurDissymetrie_reel_m;

	// en pixel, fixe
	public float epaisseur_topo_px;



	public A_Junction getFirstJunction() {return getVJunction().get(0);}
	public A_Junction getLastJunction() {return getVJunction().get(getVJunction().size()-1);}
	public A_Junction from() {return getVJunction().get(0);}
	public A_Junction to() {return getVJunction().get(getVJunction().size()-1);}
	public int getNbJunction() {return getVJunction().size();}

	// Le lien topo j->Path doit avoir été fait par l'appelant
	public void addJunction(A_Junction j) {
		assert (j!=null);
		getVJunction().add(j);
	}

	public SuperPath superPath;
	public SuperPath getSuperPath() {return superPath;}
	public void setSuperPath(SuperPath superPath) {this.superPath = superPath;}


	// *************************************************************************
	// **** DIVERS
	// *************************************************************************
	// Un ID permet de trier (regrouper en fait) les Segment d'un même Path lors d'un tri sur les segments
	// utile également pour une optimisation de l'index spatial
	// en fait, les 2 raisons précédentes ont été abandonnées suite aux evol de l'algo
	// maintenant, ca ne sert qu'à afficher un libelle unique pour le Path, utiles qd 2
	// Path ont le meme code LIT (champ typique choisi comme libelle)
	static private int currentID;
	static{currentID=0;}
	public int id;


	// *************************************************************************
	// *************************************************************************
	
	public A_Path (Attributes attributes, Zones zones, ContrainteGeomLine cgl, EchelleGeneralization eg, Color color) {
		assert (champTriLine != null);
		assert (champLibelleLine != null);

		id = currentID++;
		
		this.attributes = attributes;
		this.zones = zones;
		this.contrainteGeom = cgl;
		this.echelleGeneralization = eg;
		this.color = color;

		epaisseur_topo_px = 1.0f;
		epaisseurTrait_reel_m = 0.0f; // sera renseigné par echelleCouranteChanged()
		espaceAutour_reel_m = 0.0f; // sera renseigné par echelleCouranteChanged()
		epaisseurDissymetrie_reel_m = 0.0f; // sera renseigné par echelleCouranteChanged()
		
		bOrientationSetted = false;

		// valeur pour la comparaison pour l'ordre d'affichage
		computeValueToCompare_forDisplay();

		// Topologie
		nbNodeConnected = 0;

		int NB_MAX_NODE_CONNECTED = ParamsAppli.getParam_i("Lignesite.NB_MAX_NODE_CONNECTED_TO_PATH");
		tab_NodeConnected = new A_Junction[NB_MAX_NODE_CONNECTED];

		// Remarque :
		// Le vecteur de Segment sera initialisé à l'initiative du Data_ls, à la fin de l'ajout de toutes les données
	}

	
	// Peut etre appelé pendant les pretraitements
	// Data appelle une dernière fois cette méthode, à la fin de l'ajout de toutes les données
	// Ensuite il ne faut l'appeler que s'il y a eu un changement dans les junctions du path
	public abstract void computeVSegment();


	// Renvoie les epaisseur du path à l'échelle finale cible pour ce path
	public double getFinalWidth_onlyLine() {
		return (contrainteGeom.epaisseurTrait_papier_mm) * echelleGeneralization.echelle_inv / 1000.0;
	}
	public double getFinalWidth_withDissy() {
		return (contrainteGeom.epaisseurTrait_papier_mm + contrainteGeom.epaisseur_dissymetrie_mm) * echelleGeneralization.echelle_inv / 1000.0;
	}
	public double getFinalWidth_withDissy_withEspace() {
		return (contrainteGeom.epaisseurTrait_papier_mm + contrainteGeom.epaisseur_dissymetrie_mm + contrainteGeom.espaceAutour_papier_mm) * echelleGeneralization.echelle_inv / 1000.0;
	}

	
	public void majContrainteGeom_echelleCouranteChanged(double echelleCarteCourante_inv) {
		// On ne "grossi" pas le path au dela de l'échelle max de sa Regle de Generalisation
		if (echelleCarteCourante_inv > echelleGeneralization.echelle_inv)
			echelleCarteCourante_inv = echelleGeneralization.echelle_inv;

		epaisseurTrait_reel_m = (float) (contrainteGeom.epaisseurTrait_papier_mm * echelleCarteCourante_inv / 1000.0);
		espaceAutour_reel_m = (float) (contrainteGeom.espaceAutour_papier_mm * echelleCarteCourante_inv / 1000.0);
		epaisseurDissymetrie_reel_m = (float) (contrainteGeom.epaisseur_dissymetrie_mm * echelleCarteCourante_inv / 1000.0);
	}

	// Change l'ordre des points pour changer la position de la dissymétrie
	// Attention : il ne faut changer le Side de la ContrainteGeomLine,
	//  qui est une caractéristique du logiciel SIG qui affichera les données à la fin !
	public void reverse() {
		Collections.reverse(getVJunction());
	}

	
	// Insert la junction juste après l'index
	// Si insertion effective, on établit le lien topo
	public void insertJunction (A_Junction j, Point_d ptA, Point_d ptB) {
		ArrayList vJ = getVJunction();
		// Petit check que la junction n'est pas une des extrémités du segment où l'insérer
		if (j.equalsXY(ptA) || j.equalsXY(ptB))
			return;

		// recherche de l'index où insérer le point
		int nbJ = vJ.size();
		for (int i=0; i<nbJ ; i++) {
			if (vJ.get(i).equals(ptA) && vJ.get(i+1).equals(ptB)) {
				j.addPathContenant(this);
				vJ.add(i+1, j);
				return;
			}
		}
		assert false : "impossible de retrouver l'endroit ou insérer la junction";
	}

	public double getLength() {
		double lg=0;
		ArrayList<A_Junction> vJ = getVJunction();
		int nbJ = vJ.size();
		for (int i=0; i<nbJ-1 ; i++) {
			lg += vJ.get(i).distTo(vJ.get(i+1));
		}
		return lg;
	}
	
	
	// renvoie true SSI une et une seule extrémité est de type STD
	public boolean isOnlyOneExtrStd() {
		boolean fromIsStd = from().isStd();
		boolean toIsStd = to().isStd();
		return (fromIsStd || toIsStd) && !(fromIsStd && toIsStd);
	}

	//**************************************************************************
	//       TOPOLOGIE
	//**************************************************************************
	// Attention, il s'agit de tracer si un path est connecté directement
	//  ou indirectement à un node, et ce, dans un rayon donné, au dela duquel
	//  si c'est une connexion indirecte, on considere comme non connecté
	private int nbNodeConnected;
	private A_Junction[] tab_NodeConnected;
	public void reinitNodesConnected() {nbNodeConnected = 0;}
	public int getNbNodeConnected() {return nbNodeConnected;}
	public A_Junction getNodeConnected(int i) {return tab_NodeConnected[i];}
	public boolean isConnectedToNode(A_Junction node) {
		assert (node.isNode()) : "" + node + " n'est pas un Node";
		for (int i=0; i<nbNodeConnected ; i++)
			if (node == tab_NodeConnected[i])
				return true;
		return false;
	}
	public void addNodeConnected(A_Junction node) {
		// TODO IHM : faire un vrai test a chaque fois pour éviter une exception ? voir si pas de perte de perf
		int NB_MAX_NODE_CONNECTED = ParamsAppli.getParam_i("Lignesite.NB_MAX_NODE_CONNECTED_TO_PATH");
		assert (nbNodeConnected < NB_MAX_NODE_CONNECTED) : "Trop de Node connected cf. MAX = " + NB_MAX_NODE_CONNECTED;
		assert (node.isNode()) : "" + node + " n'est pas un Node";
		assert (isConnectedToNode(node) == false) : "" + node + " est déjà connecté à ce path";
		if ( (this instanceof PathDER && node instanceof JunctionREF) ||
			 (this instanceof PathREF && node instanceof JunctionDER) )
			assert false : "addNodeConnected() avec type incompatibles";
		tab_NodeConnected[nbNodeConnected] = node;
		nbNodeConnected++;
	}
	// Ajoute les nodes connected de 'path' à 'this'
	public void recupereNodeConnectedFrom(A_Path path) {
		for (int i=0; i<path.nbNodeConnected ; i++) {
			A_Junction node = path.tab_NodeConnected[i];
			if (!this.isConnectedToNode(node))
				this.addNodeConnected(node);
		}
	}


	
	
	public boolean areAllJunctionInCircle (Point_d ptCenter, double rayon) {
		ArrayList<A_Junction> vJ = getVJunction();
		double rayon_sq = rayon*rayon;
		int nbJ = vJ.size();
		for (int i=0; i<nbJ ; i++) {
			A_Junction j = vJ.get(i);
			if (j.distTo_sq(ptCenter) > rayon_sq)
				return false;
		}
		return true;
	}
	public boolean isAtLeastOneJunctionInCircle (Point_d ptCenter, double rayon) {
		ArrayList<A_Junction> vJ = getVJunction();
		double rayon_sq = rayon*rayon;
		int nbJ = vJ.size();
		for (int i=0; i<nbJ ; i++) {
			A_Junction j = vJ.get(i);
			if (j.distTo_sq(ptCenter) <= rayon_sq)
				return true;
		}
		return false;
	}
	
	// détermine si le path intersecte un cercle (si au moins un de ses segments a une distance au centre du cercle inférieur au rayon du cercle)
	public boolean doesIntersectCircle(Point_d ptCenter, double rayon) {
		ArrayList<A_Junction> vJunction = getVJunction();
		double nbJ = vJunction.size();
		Point_d j1, j2;
		j2 = (Point_d)vJunction.get(0);
		for (int j=0 ; j<nbJ-1; j++) {

			// cette petite astuce (swap de variable) limite le nombre d'appel à get() -> cela optimise le traitement
			j1 = j2;
			j2 = (Point_d)vJunction.get(j+1);
			
			if (UtilsGeom.getDistancePtSegment(j1, j2, ptCenter) <= rayon)
				return true;
		}
		return false;
	}
	
	// Renvoie l'ensemble des path connecté à this, via des CONNECT_2 ou CONNET_3
	// (on ne considère pas les INTERSECT et NODE !)
//	public ArrayList<A_Path> getPathConnected() {
//		ArrayList<A_Path> vRes = new ArrayList<A_Path>();
//		HashSet<A_Path> setPathParcouru = new HashSet<A_Path>();
//
//	}
//	private void getPathConnected_rec(A_Path, path, ArrayList<A_Path> vRes, A_Junction jExtr, HashSet<A_Path> setPathParcouru) {
//
//	}


	//**************************************************************************
	//  I_Searchable
	//**************************************************************************
	@Override
	public boolean matchSearch(Pattern pattern) {
		if (attributes==null) return false;
		int nbAttrib = attributes.getModel().getNumberOfField();
		for (int i=0; i<nbAttrib ; i++) {
			if (pattern.matcher(attributes.getValue(i).toUpperCase()).matches())
				 return true;
		}
		return false;
	}

	
	//**************************************************************************
	//  Comparable
	//**************************************************************************

	@Override
	public int compareTo(A_Path p) {
		Comparable val1 = valueToCompare_forDisplay;
		Comparable val2 = p.valueToCompare_forDisplay;

		if (bBiggestOnTop)
			return val1.compareTo(val2);
		else
			return - val1.compareTo(val2);
	}

	
	//**************************************************************************
	//  ISpatialIndexable
	//**************************************************************************

	// NORMALEMENT PAS BESOIN D'IMPLEMENTER : l'INDEX SPATIAL TRAITE UN PATH COMME UNE SERIE DE SEGMENT
	// C'EST DONC AU NIVEAU DES SEGMENTS QUE LES getXMin() etc. SONT APPELLES
	// en fait, on l'implémente de façon très peu efficace, c'est utilisé uniquement
	// par la fonction de 'zoom sur la sélection'

	@Override
	public double getXMin() {
		Logger.log_DEBUG1("ATTENTION, utilisation de getXMin() sur un path -> peu efficace");
//		new Exception().printStackTrace();
		return getRectEmprise().xMin;
	}
	@Override
	public double getXMax() {
		Logger.log_DEBUG1("ATTENTION, utilisation de getXMax() sur un path -> peu efficace");
		return getRectEmprise().xMax;
	}
	@Override
	public double getYMin() {
		Logger.log_DEBUG1("ATTENTION, utilisation de getYMin() sur un path -> peu efficace");
		return getRectEmprise().yMin;
	}
	@Override
	public double getYMax() {
		Logger.log_DEBUG1("ATTENTION, utilisation de getYMax() sur un path -> peu efficace");
		return getRectEmprise().yMax;
	}

	@Override
	public Rect_d getRectEmprise() {
		ArrayList<A_Junction> vJ = getVJunction();
		Rect_d r = new Rect_d(vJ.get(0));
		int nbJ = vJ.size();
		for (int i=1; i<nbJ ; i++)
			r.extendToPoint(vJ.get(i));
		return r;
	}

	public Rect_d boundingBox;
	public void computeBoundingBox() {
		boundingBox = getRectEmprise();
	}

	//**************************************************************************
	//  IDrawable
	//**************************************************************************

	Path2D.Float p2d=new Path2D.Float();
	Line2D.Float l2d = new Line2D.Float();

	@Override
	public void draw (Graphics2D g, ParametresAffichageCarto pac) {
		ArrayList<A_Junction> vPtSimplified = getVPointsimplified(Data_ls.data_ls, pac);

		//Tout d'abord la dissymetrie eventuelle
		if (contrainteGeom.hasDissymetrie() && pac.typeAffichage == TypeAffichage.PAPIER) {
			boolean bRight = contrainteGeom.sideDissymetrie==Side.RIGHT;

			float largeurDissymetrie_px = pac.lg2LG(epaisseurDissymetrie_reel_m);
			// en dessous de 1pixel de largeur, on ne dessine pas la dissymetrie pour ne pas ralentir inutilement l'affichage
			if (largeurDissymetrie_px>1) {

				// 2 méthodes de dessin de la dissy - basique et sous forme de barbules (plus lent)

				if (Data_ls.data_ls.params.bShowBarbules) {

					double longueurBarbule_reel_m = epaisseurDissymetrie_reel_m +  epaisseurTrait_reel_m/2;
					double epaisseurBarbule_reel_m = epaisseurDissymetrie_reel_m * Data_ls.data_ls.params.factor_EpaisseurBarbules;
					double espaceEntreBarbule_reel_m = epaisseurBarbule_reel_m * Data_ls.data_ls.params.factor_InterBarbules;

					float epaisseurBarbule_px = pac.lg2LG(epaisseurBarbule_reel_m);
					g.setStroke(pac.getBasicStroke(epaisseurBarbule_px));
					g.setColor(getColor(pac));

					double lgVersNextBarbule_reel_m = espaceEntreBarbule_reel_m / 2;

					int nbPoint = vPtSimplified.size();
					for (int i=0; i<nbPoint-1 ; i++) {

						Point_d pt1 = vPtSimplified.get(i).getCopy();// on fait une copie car Pt1 sera modifié dans la suite du traitement
						Point_d pt2 = vPtSimplified.get(i+1);

						// Pt1 est modifié par la fonction et deviendra le nouveau point de départ pour l'appel suivant
						lgVersNextBarbule_reel_m = createBarbulle(pt1, pt2, lgVersNextBarbule_reel_m, longueurBarbule_reel_m, l2d, bRight, pac);
						while(lgVersNextBarbule_reel_m == 0) {
							// on a pu placer une barbulle sur le segment [Pt1 Pt2]
							g.draw(l2d);
							lgVersNextBarbule_reel_m = espaceEntreBarbule_reel_m;
							lgVersNextBarbule_reel_m = createBarbulle(pt1, pt2, lgVersNextBarbule_reel_m, longueurBarbule_reel_m, l2d, bRight, pac);
						}
						// Arrivé la , lgVersNextBarbule contient la longueur residuelle a rechercher sur le segment suivant
					}

				} else {
					// Affichage basique des dissymetries (un simple trait grisé de la bonne épaisseur)
					g.setStroke(pac.getBasicStroke(largeurDissymetrie_px));
					g.setColor(Utils.getColorGrisee(color));

					Segment_d seg = new Segment_d();

					double decalage = epaisseurTrait_reel_m/2 + epaisseurDissymetrie_reel_m/2;

					int nbPoint = vPtSimplified.size();
					for (int i=0; i<nbPoint-1 ; i++) {

						Point_d pt1 = vPtSimplified.get(i);
						Point_d pt2 = vPtSimplified.get(i+1);
						seg.takeCoordFrom(pt1, pt2);

						seg.decalePerpendiculairement(decalage, bRight);

						l2d.setLine(pac.x2X(seg.pt1.x), pac.y2Y(seg.pt1.y), pac.x2X(seg.pt2.x), pac.y2Y(seg.pt2.y));
						g.draw(l2d);
					}
				}

			}
		}

		// Puis l'affichage normal
		Shape sh_main = computeShape(vPtSimplified, pac);
		g.setStroke(pac.getBasicStroke(getWidth_px(pac)));
		g.setColor(getColor(pac));
		g.draw(sh_main);

		// En mode debug, si le path est highlighted, on indique avec un rond le point de départ
		//  cela sert pour comprendre l'optimisation des dissymétries
		if (Logger.isDebugLevel() && pac.modeAffichage == ModeAffichage.HIGHLIGHT) {
			Point_d fromPoint = vPtSimplified.get(0);
			float diametre_px = (pac.typeAffichage == TypeAffichage.PAPIER) ? 1.3f*pac.lg2LG(epaisseurTrait_reel_m) : 10;
			Ellipse2D.Float shEllipse = new Ellipse2D.Float();
			shEllipse.x = pac.x2X(fromPoint.x) - diametre_px/2;
			shEllipse.y = pac.y2Y(fromPoint.y) - diametre_px/2;
			shEllipse.width  = diametre_px;
			shEllipse.height = diametre_px;

			g.draw(shEllipse);
		}

	}

	// Pt1 est modifié par la fonction et deviendra le nouveau point de départ pour l'appel suivant
	private double createBarbulle(Point_d pt1, Point_d pt2, double distFromPt1, double lgBarbule, Line2D.Float barbule, boolean bDroite, ParametresAffichageCarto pac) {
		assert(distFromPt1>0);

		// Si le segment Pt1,Pt2 est trop petit, on renvoie la distance residuelle à prendre à partir du segment prochain
		float lg = (float) pt1.distTo(pt2);
		if (lg==0)
			return distFromPt1;

		if (lg < distFromPt1)
			return distFromPt1-lg;

		Vector_d v = Vector_d.createCopyFrom(pt1, pt2);
		v.multiplyBy(distFromPt1/lg); // on place l'extrémité de v au point de départ de la barbule
		
		Vector_d vNormal;
		if (bDroite)
			vNormal = v.getVectUnitaireNormalDroite();
		else
			vNormal = v.getVectUnitaireNormalGauche();

		vNormal.setPointDepart(v.pt2);
		vNormal.multiplyBy(lgBarbule);

		barbule.setLine(pac.x2X(vNormal.pt1.x), pac.y2Y(vNormal.pt1.y), pac.x2X(vNormal.pt2.x), pac.y2Y(vNormal.pt2.y));

		// Pt1 deviant le nouveau point de départ pour l'appel suivant
		pt1.x = vNormal.pt1.x;
		pt1.y = vNormal.pt1.y;

		return 0;
	}

	// Petit cache pour chaque path des points simplifiés
	// cela accélère lors d'un scroll, car la tolerance dépend de l'échelle de visu
	// en cas de zoom, le cache ne sert à rien.
	// Idem, si l'échelle courante a changé (apres calcul complet ou en mode interactif) alors il faut tout recalculer
	// Idem si la version des données a changé
	private double current_tolerance = -9999;
	private int versionDataDER = -9999;
	private ArrayList<A_Junction> vPtSimplified = null;
	private ArrayList<A_Junction> getVPointsimplified(Data_ls data, ParametresAffichageCarto pac) {
		if (pac.toleranceApproxLine_px>0) {
			double tolerance = pac.LG2lg(pac.toleranceApproxLine_px);
			if (tolerance==current_tolerance && versionDataDER==data.getVersionDataDER() && vPtSimplified!= null)
				return vPtSimplified;

			vPtSimplified = (ArrayList) UtilsGeom.douglasPeuckerReduction_global ((ArrayList)this.getVJunction(), tolerance);
			current_tolerance = tolerance;
			versionDataDER = data.getVersionDataDER();
		}
		else
			vPtSimplified = this.getVJunction();

		return vPtSimplified;
	}

	
	private Path2D.Float computeShape (ArrayList<A_Junction> vPoint_, ParametresAffichageCarto pac) {
		// TODO_KO PERF : creer qu'une seule fois le path2D puis reset() a chaque appel
		int nbPt = vPoint_.size();
		p2d.reset();
		A_Junction pt;
		pt = vPoint_.get(0);
		p2d.moveTo(pac.x2X(pt.x), pac.y2Y(pt.y));
		for (int i = 1; i < nbPt; i++) {
			pt = vPoint_.get(i);
			p2d.lineTo(pac.x2X(pt.x), pac.y2Y(pt.y));
		}
		return p2d;
	}


	private float getWidth_px(ParametresAffichageCarto pac) {
		float width_px = 0;
		if (pac.typeAffichage == TypeAffichage.TOPO)
			width_px = epaisseur_topo_px;
		else if (pac.typeAffichage == TypeAffichage.PAPIER)
			// l'epaisse carto en pixel dépend du zoom en cours
			width_px = pac.lg2LG(epaisseurTrait_reel_m);
		else
			assert false : "Type d'affichage inconnu : " + pac.typeAffichage;

		// Pour rendre bien lisible la sélection avec l'antialiasing
		if (pac.modeAffichage == ModeAffichage.SELECTION || pac.modeAffichage == ModeAffichage.HIGHLIGHT)
			width_px = Math.max(width_px, 2f);

		if (pac.modeAffichage == ModeAffichage.HIGHLIGHT)
			width_px += 1f;

		return width_px;
	}

	public Color getColor(ParametresAffichageCarto pac) {
		return pac.getColorSelonModeAffichage(color);

//		if (pac.modeAffichage == ModeAffichage.SELECTION) {
//			return ParamsAppli.getParam_c("COULEUR_SELECTION");
//		} else if (pac.modeAffichage == ModeAffichage.HIGHLIGHT) {
//			return ParamsAppli.getParam_c("COULEUR_SURBRILLANCE");
//		} else {
//			return color;
//		}
	}


	public Point_d getClosestPoint(Point_d ptI, double distMaxRecherche) {
		double distMin_sq = Double.POSITIVE_INFINITY;//99999999999.0;
		if (distMaxRecherche<=0)
			distMaxRecherche = Double.POSITIVE_INFINITY;//99999999999.0;
		double distMaxRecherche_sq = distMaxRecherche*distMaxRecherche;

		Point_d closestPt=null;
		ArrayList vJunction = getVJunction();
		int sizeVJunction = vJunction.size();

		Point_d j1, j2;
		j2 = (Point_d)vJunction.get(0);
		for (int j=0 ; j<sizeVJunction-1; j++) {

			// cette petite astuce (swap de variable) limite le nombre d'appel à get() -> cela optimise le traitement
			j1 = j2;
			j2 = (Point_d)vJunction.get(j+1);

			// elimination rapide des segments trivialement trop éloignés
			// on n'utilise pas de methode max() et min(), des tests bien fichu (mais moins lisibles) sont plus rapides

			if (j1.x<j2.x) {
				if ( (j1.x > ptI.x + distMaxRecherche) || (j2.x < ptI.x - distMaxRecherche) )
					continue;
			} else {
				if ( (j2.x > ptI.x + distMaxRecherche) || (j1.x < ptI.x - distMaxRecherche) )
					continue;
			}

			if (j1.y<j2.y) {
				if ( (j1.y > ptI.y + distMaxRecherche) || (j2.y < ptI.y - distMaxRecherche) )
					continue;
			} else {
				if ( (j2.y > ptI.y + distMaxRecherche) || (j1.y < ptI.y - distMaxRecherche) )
					continue;
			}

			// point le plus proche entre le point courant et le segment en cours
			Point_d closestPtTemp = UtilsGeom.getClosestPt(j1, j2, ptI);
			double distTemp_sq = ptI.distTo_sq(closestPtTemp);

			if (distTemp_sq>distMaxRecherche_sq)
				continue;

			if (distTemp_sq < distMin_sq) {
				closestPt = closestPtTemp;
				distMin_sq = distTemp_sq;
			}
		}
		return closestPt;
	}

/* OLD V19
	// Renvoie un Vector dont la base est le point le plus proche et qui indique la direction (unitaire) de this en ce point
	// Plus lent, n'a d'interet que si this a une dissymetrie
	// ATTENTION : Ne pas modifier le vecteur retourné car il peut pointer vers des junction de this !!!
	public Vector_d getClosestPointWithDirection(Point_d ptI, double distMaxRecherche) {
		//assert (this.hasDissymetrie());
		double distMin_sq = Double.POSITIVE_INFINITY;//99999999999.0;
		if (distMaxRecherche<=0)
			distMaxRecherche = Double.POSITIVE_INFINITY;//99999999999.0;
		double distMaxRecherche_sq = distMaxRecherche*distMaxRecherche;

		Vector_d closestPtWithDirection = null;
		ArrayList<JunctionDER> vJunction = (ArrayList) getVJunction();
		int sizeVJunction = vJunction.size();

		JunctionDER j1, j2;
		j2 = vJunction.get(0);
		for (int j=0 ; j<sizeVJunction-1; j++) {

			// cette petite astuce (swap de variable) limite le nombre d'appel à get() -> cela optimise le traitement
			j1 = j2;
			j2 = vJunction.get(j+1);

			// elimination rapide des segments trivialement trop éloignés
			// on n'utilise pas de methode max() et min(), des tests bien fichu (mais moins lisibles) sont plus rapides

			if (j1.x<j2.x) {
				if ( (j1.x > ptI.x + distMaxRecherche) || (j2.x < ptI.x - distMaxRecherche) )
					continue;
			} else {
				if ( (j2.x > ptI.x + distMaxRecherche) || (j1.x < ptI.x - distMaxRecherche) )
					continue;
			}

			if (j1.y<j2.y) {
				if ( (j1.y > ptI.y + distMaxRecherche) || (j2.y < ptI.y - distMaxRecherche) )
					continue;
			} else {
				if ( (j2.y > ptI.y + distMaxRecherche) || (j1.y < ptI.y - distMaxRecherche) )
					continue;
			}

			// point le plus proche entre le point courant et le segment en cours
			Point_d closestPtTemp = UtilsGeom.getClosestPt(j1, j2, ptI);
			double distTemp_sq = ptI.distTo_sq(closestPtTemp);

			if (distTemp_sq>distMaxRecherche_sq)
				continue;

			if (distTemp_sq < distMin_sq) {

				if (closestPtWithDirection==null) closestPtWithDirection = new Vector_d();

				// Si closestPtTemp n'est pas une des extrémités j1 ou j2, alors ptI se projete
				//  sur le segment [j1-j2], le vecteur direction est calculé
				//  à partir de ce segment
				if (closestPtTemp != j1 && closestPtTemp != j2) {
					// on renvoie le vecteur <j1,j2>, mais avec comme base le pt de projection
					closestPtWithDirection.pt1.x = closestPtTemp.x;
					closestPtWithDirection.pt1.y = closestPtTemp.y;
					closestPtWithDirection.pt2.x = j2.x-j1.x + closestPtTemp.x;
					closestPtWithDirection.pt2.y = j2.y-j1.y + closestPtTemp.y;
				}


				// Si closestPtTemp est une des extrémités j1 ou j2, alors ptI est en
				//  face d'une "pointe" de this. Dans ce cas, il faut prendre le vecteur direction
				//  précalculé dans j1 ou j2, sinon si c'est tres pointu, on pourrait croire
				//  que l'on est du mauvais coté de this, selon le vecteur sur lequel on tombe
				// Ces vecteurs direction precalculé peuvent etre nombreux si le point est multiple
				//  -> on pendra le vecteur dont le path correspond à this, et le 1er s'il y en a
				//     plusieurs (cas des path rebouclant sur eux-memes
				else if(closestPtTemp==j1)
					closestPtWithDirection.takeCoordFrom(j1.getDirectionDataForThisPath(this).vectorDirection);
				else if(closestPtTemp==j2)
					closestPtWithDirection.takeCoordFrom(j2.getDirectionDataForThisPath(this).vectorDirection);

				distMin_sq = distTemp_sq;
			}
		}
		return closestPtWithDirection;
	}
*/

	// Renvoi le nb d'intersection qui sont en plus dans un disque donné (si ptCenter == null, alors décompte toutes les intersection
	// Petit souci si un seg de path1 intersect un seg de path2 au niveau d'une extrémité : cette intersection sera comptée 2 fois ...
	// à corriger si ca pose problème
	static public int getNbIntersectionBetween2Path(A_Path path1, A_Path path2, Point_d ptCenter, double rayon) {
		// pour ne pas compter 2 fois les memes intersections
		
if (((A_Junction)ptCenter).toString().equals("B.VAL"))
		rayon=rayon;
		
		HashMap<String, Point_d> mapIntersect=null;
		
		ArrayList<A_Junction> vJunction1 = path1.getVJunction();
		int nbJ1 = vJunction1.size();

		Point_d j1, j2;
		j2 = (Point_d)vJunction1.get(0);
		for (int j=0 ; j<nbJ1-1; j++) {

			// cette petite astuce (swap de variable) limite le nombre d'appel à get() -> cela optimise le traitement
			j1 = j2;
			j2 = (Point_d)vJunction1.get(j+1);

			Point_d pt = path2.getIntersectWithThisSeg(j1, j2);
			if (pt!=null) {
				if (ptCenter == null || (ptCenter != null && pt.distTo(ptCenter)<=rayon)) {
					// on stocke l'intersect dans la map, pour éviter de compter 2 fois les memes points
					if (mapIntersect==null) mapIntersect = new HashMap<String, Point_d>();
					mapIntersect.put(UtilsCalcul.round(pt.x,10) + "_" + UtilsCalcul.round(pt.y,10), pt);
				}
			}
		}
		return mapIntersect==null ? 0 : mapIntersect.size();
	}
	
	
	public boolean doesIntersectThisSeg(Segment_d seg) {
		return doesIntersectThisSeg(seg.pt1, seg.pt2);
	}

	public boolean doesIntersectThisSeg(Point_d pt1, Point_d pt2) {
		ArrayList<A_Junction> vJunction = getVJunction();
		int nbJunction = vJunction.size();

		Point_d j1, j2;
		j2 = (Point_d)vJunction.get(0);
		for (int j=0 ; j<nbJunction-1; j++) {

			// cette petite astuce (swap de variable) limite le nombre d'appel à get() -> cela optimise le traitement
			j1 = j2;
			j2 = (Point_d)vJunction.get(j+1);

			if (UtilsGeom.areSegmentIntersected(j1, j2, pt1, pt2))
				return true;
		}
		return false;
	}
	public Point_d getIntersectWithThisSeg(Point_d pt1, Point_d pt2) {
		ArrayList<A_Junction> vJunction = getVJunction();
		int nbJunction = vJunction.size();

		Point_d j1, j2;
		j2 = (Point_d)vJunction.get(0);
		for (int j=0 ; j<nbJunction-1; j++) {

			// cette petite astuce (swap de variable) limite le nombre d'appel à get() -> cela optimise le traitement
			j1 = j2;
			j2 = (Point_d)vJunction.get(j+1);

			Point_d pt = UtilsGeom.intersectionSegments(j1, j2, pt1, pt2);
			if (pt != null)
				return pt;
		}
		return null;
	}


	@Override
	public double distTo(Point_d ptI) {
		Point_d closestPt = getClosestPoint(ptI, -1);
		if (closestPt == null) {
			return 99999999;
		}
		else
			return closestPt.distTo(ptI);
	}

	@Override
	public String getInfoBulle() {
		return attributes.getValue(champLibelleLine) + "_" + id;
	}

	@Override
	public String getInfoDetaillee() {
		String res = "";

		// Généralités
		res += "Nb. pts = " + getNbJunction() + "\n";
		res += "Lg.     = " + UtilsCalcul.round(getLength(), 2) + " m\n\n";

		if (Logger.isDebugLevel()) {
			if (!echelleGeneralization.bToBeGeneralized)
				res += "** PAS BESOIN D'ETRE GENERALISEE **\n\n";


			// contrainte geom
			if (contrainteGeom!=null)
				res += "Contrainte Geom : " + contrainteGeom + "\n\n";

			// zone (ne peut pas être null)
			res += zones.toString() + "\n";

			// regle de generalisation
			if (echelleGeneralization!=null)
				res += "Règle généralisation : echelle -> " + echelleGeneralization.echelle_inv + "\n\n";

			// superpath
			if (superPath != null)
				res += "Super Path = " + superPath.numSuperPath + "\n";
			else
				res += "pas de Super Path\n";

			// junctions extrémités
			res += "first junction : " + getFirstJunction() + "\n" +
				   "last  junction : " + getLastJunction() + "\n\n";

			// connexion directe ou indirecte à un node
			res += "Connecté aux nodes :\n";
			for (int i=0; i<getNbNodeConnected() ; i++)
				res += " - " + getNodeConnected(i);
			res += "\n\n";
			
			// optimisation des dissymetrie
			res += "Optimisation des dissymétries\n";
			res += "Pour ce path, dissymétries paramétrées côté : " + contrainteGeom.sideDissymetrie + "\n";
			res += " - nb total de barbules testées   : " + nbTotal_barbule + "\n";
			res += " - nb barbule DROITE en intersect : " + nbIntersect_barbuleDroite + "\n";
			res += " - nb barbule GAUCHE en intersect : " + nbIntersect_barbuleGauche + "\n\n";
		}

		// liste des attributs et de leur valeur
		res += attributes.toString();
		res += "\n";

		// catégories du path
		res += "Catégories :\n";
		res += attributes.getListCategoriesToString();

		res += "\n";
		res += getInfoDetaille_specific();

		return res;
	}

	String getInfoDetaille_specific() {return "";}
	
	@Override
	public String toString() {return getInfoBulle();}

	//**************************************************************************
	//  Divers
	//**************************************************************************

	// renvoie la valeur servant a trier les path (pour l'affichage)
	// appelé par le constructeur
	private void computeValueToCompare_forDisplay() {
		String val = attributes.getValue(A_Path.champTriLine);

		switch (typeChampTriLine) {
			case INT:
				if (val == null || val.length()==0 || !Utils.isInteger(val))
					valueToCompare_forDisplay = new Integer(0);
				else
					valueToCompare_forDisplay = new Integer(val);
				break;

			case DBL:
				if (val == null || val.length()==0 || !Utils.isFloat(val))
					valueToCompare_forDisplay = new Double(0);
				else
					valueToCompare_forDisplay = new Double(val);
				break;

			case DAT:
				if (val == null || val.length()==0 || !Utils.isDate(val))
					valueToCompare_forDisplay = new Date(0);
				else
					valueToCompare_forDisplay = Utils.parseDate(val);
				break;

			case TXT:
				valueToCompare_forDisplay = val;
				break;

			default:
				assert (false);
				valueToCompare_forDisplay = "";
		}
	}


	// Test avec une égalité sur les objets (et non les coordonnées) !
	public int getIndexOfThisJunction (A_Junction j) {
		return getIndexOfThisJunction (j, 0);
	}
	// TODO_NON OPTIM (pas mal utilise par simplif topo) -> finalement, on ne l'utilise presque plus ...
	public int getIndexOfThisJunction (A_Junction j, int indexDeb) {
		if (j==null) return -1;
		assert (indexDeb>=0 && indexDeb<getVJunction().size()) : "indexDeb incorrect : " + indexDeb + " cf. nbPoints = " + getVJunction().size();

		for (int i = indexDeb; i <getVJunction().size() ; i++) {
			if (getVJunction().get(i) == j)
				return i;
		}
		return -1;
	}

	public boolean hasCommonExtremityWith(A_Path path) {
		return this.from()==path.from() || this.from()==path.to() || this.to()==path.from() || this.to()==path.to();
	}
	

	public static ArrayList<A_Path> getCommonPath(A_Junction j1, A_Junction j2) {
		ArrayList<A_Path> vRes = new ArrayList<A_Path>();
		int nbPath1 = j1.getNbPathContenant();
//		int nbPath2 = j2.getNbPathContenant();
		
		for (int i=0; i<nbPath1; i++) {
			A_Path p = j1.getPathContenantNum(i);
			if (j2.faitPartieDuPath(p))
				vRes.add(p);
		}
		return vRes;
	}

	
	
	// PAS OPTIMISE !!!!!!!!! pour du assert uniquemnt
	// Calcule le nombre d'intersections non prévue sur un ensemble de Path, ie <> type INTERSECT
	// !! les segments des path peuvent ne pas etre calculés !!
	// !! spiSeg n'est pas pré-rempli !!
	public static int computeNbNewIntersect (ArrayList<A_Path> vPath, SimpleGridSPI spiSeg) {
		
		ArrayList<Segment_d> vAllSeg = new ArrayList<Segment_d>(vPath.size()*30);
		//for (A_Path p : vPath) vAllSeg.addAll(p.getVSegment());
		// creation des segments
		for (A_Path p : vPath) {
			int nbJ = p.getVJunction().size();
			for (int i=0 ; i<nbJ-1 ; i++) {
				A_Junction j1 = p.getVJunction().get(i);
				A_Junction j2 = p.getVJunction().get(i+1);
				vAllSeg.add(new Segment_d(j1, j2));
			}
		}

		spiSeg.clear();	
		spiSeg.addObjects(vAllSeg);
		
		return computeNbNewIntersect_fast (spiSeg, vAllSeg);
	}
	
	// vAllSeg contient tous les segments de Path, et spiSeg est prérempli
	public static int computeNbNewIntersect_fast (SimpleGridSPI spiSeg, ArrayList<Segment_d> vAllSeg) {
		int nbNewIntersect = 0;
		int nbSeg = vAllSeg.size();
		for (int i=0; i<nbSeg ; i++) {
			Segment_d seg = vAllSeg.get(i);

			ArrayList<Segment_d> vSegProche = (ArrayList) spiSeg.getObjectsThatMayIntersectWith(seg);

			int nbSegProche = vSegProche.size();
			for (int j=0; j<nbSegProche ; j++) {
				Segment_d seg2 = vSegProche.get(j);
				if (seg==seg2) continue;
				if (seg.equalsCoord(seg2)) {
					// segments superposés, on considère que c'est une nouvelle intersection
					nbNewIntersect++;
					Logger.log_DEBUG1("### SEGMENTS SUPERPOSES : " + Segment_d.getStringOf(seg));
				} else if (UtilsGeom.areSegmentInternallyIntersected(seg, seg2)) {
					nbNewIntersect++;
				}
			}
		}
		//les intersections ont été compté 2 fois !
		return nbNewIntersect/2;
	}
	
	// idem mais ne compte que les intersections nouvelles qui sont VISIBLE, c'est à dire, pas dans le cercle d'un node
	// vAllSeg contient tous les segments de Path, et spiSeg est prérempli
	// spiNode est egalement rempli
	//  maxDiametreNode_reel_m sert a calibrer le rayon de recherche de nodes recouvrant dans le spiNode
	public static int computeNbNewIntersect_visible (SimpleGridSPI spiSeg, ArrayList<Segment_d> vAllSeg, SimpleGridSPI spiNode, double maxDiametreNode_reel_m) {
		int nbNewIntersect = 0;
		int nbSeg = vAllSeg.size();
		for (int i=0; i<nbSeg ; i++) {
			Segment_d seg = vAllSeg.get(i);

			ArrayList<Segment_d> vSegProche = (ArrayList) spiSeg.getObjectsThatMayIntersectWith(seg);

			int nbSegProche = vSegProche.size();
			for (int j=0; j<nbSegProche ; j++) {
				Segment_d seg2 = vSegProche.get(j);
				Point_d ptIntersect;
				if (seg==seg2) continue;
				if (seg.equalsCoord(seg2)) {
					// segments superposés, on considère que c'est une nouvelle intersection
					
					// cas rare, aussi, pour simplifier, on considére que le pt d'intersection est le centre des segments ...
					ptIntersect = seg.getCenter();
					Logger.log_DEBUG1("### SEGMENTS SUPERPOSES : " + Segment_d.getStringOf(seg));
				} else
					// on ne veut qu'une éventuelle intersection INTERNE (pas due à une éventuelles extrémité commune entre les 2 seg...)
					ptIntersect = UtilsGeom.intersectionSegments_interne(seg, seg2);
				
				
				if (ptIntersect != null) {
					// pt d'intersection détecté, on vérifie maintenant qu'il est visible
					if (!isPtInNode(ptIntersect, spiNode, maxDiametreNode_reel_m))
						nbNewIntersect++;
				}
			}
		}
		//les intersections ont été compté 2 fois !
		return nbNewIntersect/2;
	}
	static private boolean isPtInNode(Point_d pt, SimpleGridSPI spiNode, double maxDiametreNode_reel_m) {
		Rect_d rectRecherche = new Rect_d(pt, maxDiametreNode_reel_m);
		ArrayList<A_Junction> vNodeProche = (ArrayList) spiNode.getObjectsThatMayIntersectWith(rectRecherche);
		//System.out.println(")))))))) nb nodes proche : " + vNodeProche.size());

		for (A_Junction node : vNodeProche) {
			if (pt.distTo(node) < node.getRayonNodeFinal())
				return true;
		}
		return false;
	}
	

	// Renvoie le nombre de junction de type INTERSECT du path
	public int getNbJunctionIntersect() {
		int res=0;
		ArrayList<A_Junction> vJunction = getVJunction();
		int nbJunction = vJunction.size();
		for (int i=0 ; i<nbJunction-1; i++)
			if (vJunction.get(i).isIntersect())
				res++;
		return res;
	}

	// Renvoie le nombre de fois qu'une junction apparait dans le path
	public int getNbOccurenceThisJunction(A_Junction j) {
		int res=0;
		ArrayList<A_Junction> vJunction = getVJunction();
		int nbJunction = vJunction.size();
		for (int i=0 ; i<nbJunction-1; i++)
			if (vJunction.get(i) == j)
				res++;
		return res;
	}

	
}
